; -------------------------------------------------------------------------------------------------------------------------
        ; 主引导程序
; -------------------------------------------------------------------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00         ; vstart告诉编译器，该section的起始地址编译为0x7c00

                                  
        ;---------- 寄存器初始化 ----------                                  
                                  ; 此前BIOS通过jmpf 0:0x7c00跳转到MBR
                                  ; 此时cs寄存器为0，用来将ax置0
                                  ; ds、es、ss、fs、gs这类sreg，CPU没有从立即数赋值的硬件实现，即mov ds, 0是错误的
                                  ; 因此只能通过其它寄存器来赋值，这里选择用通用寄存器ax
	mov ax, cs                
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov fs, ax
	mov sp, 0x7c00            ; 初始化栈指针sp，其从高位向地位变化，恰好0x500~0x7bff是可用区域，这30KB左右的内存应该够用...
	mov ax, 0xb800
	mov gs, ax                ; 显存在内存中的映射为0xb8000 ~ 0xbffff，因此给段基址赋为0xb800(这里用gs来存段基址)
	
	
       ;---------- 清屏  利用BIOS的0x06号功能，上卷全部行，从而清屏 ----------
                                  ; ----------------------------------------------------------
                                  ; INT 0x10    功能号: 0x06    功能描述：BIOS中断，上卷一个窗口
                                  ; ----------------------------------------------------------
                                  ; 输入:
	mov ax, 0x0600            ; ah=功能号=0x06;    al=行数=0（0表示全部）
	mov bx, 0x0700            ; bh=行属性=0x07;    bl无意义
	mov cx, 0                 ; (dh,cl)=窗口左上角位置=(0,0)
	mov dx, 0x184f            ; (dh,dl)=窗口右下角位置=(0x18,0x4f)=(24,79)，第25行第80个
	
	                          ; 执行int 0x10 0x06功能
	int 0x10                  ; 无输出
	
	                          
        ;---------- 输出背景色绿色，前景色红色，并且跳动的字符串"Hello, M-OS" ----------	     
	mov byte [gs:0x02], 'H'
	mov byte [gs:0x03], 0xA4  ; A=绿色背景色+闪烁;    4=红色前景色
	
	mov byte [gs:0x04], 'e'
	mov byte [gs:0x05], 0xA4
	
	mov byte [gs:0x06], 'l'
	mov byte [gs:0x07], 0xA4
	
	mov byte [gs:0x08], 'l'
	mov byte [gs:0x09], 0xA4
	
	mov byte [gs:0x0a], 'o'
	mov byte [gs:0x0b], 0xA4
	
	mov byte [gs:0x0c], ','
	mov byte [gs:0x0d], 0xA4
	
	mov byte [gs:0x0e], ' '
	mov byte [gs:0x0f], 0xA4
	
	mov byte [gs:0x10], 'M'
	mov byte [gs:0x11], 0xA4
	
	mov byte [gs:0x12], '-'
	mov byte [gs:0x13], 0xA4
	
	mov byte [gs:0x14], 'O'
	mov byte [gs:0x15], 0xA4
	
	mov byte [gs:0x16], 'S'
	mov byte [gs:0x17], 0xA4
	
	
        ;---------- 从磁盘中加载boot-loader，并将控制权交给它 ----------
	mov eax, LOADER_START_SECTOR      ; boot-loader在磁盘中的起始扇区的LBA地址，定义在boot.inc配置文件中
	mov bx, LOADER_BASE_ADDR          ; 将boot-loader写入内存的地址，同样定义在boot.inc配置文件中
	mov cx, 4                         ; 读入4个扇区（我们写的boot-loader超过了512B，索性将读入的扇区弄大点，省得将来又不够还得改）
	call rd_disk_m_16                 ; 调用下面的rd_disk_m_16函数
	
	jmp LOADER_BASE_ADDR              ; 将控制权交给boot-loader


; -------------------------------------------------------------------------------------------------------------------------
        ; 功能：以16位的模式读取硬盘n个扇区并写入内存
        ; -----------------------------------------
        ; 输入:
        ; eax=LBA扇区号
        ; bx=将数据写入的内存地址
        ; cx=要读取的扇区数
; -------------------------------------------------------------------------------------------------------------------------
rd_disk_m_16:
	                          
	mov esi, eax                      ; 备份eax
        mov di, cx                        ; 备份cx
        
        ;---------- 读写硬盘 ----------
        ; 第1步: 设置端口0x1f2（sector count端口）为要读取的扇区数
        mov dx, 0x1f2                     
        mov al, cl
        out dx, al                        ; 通过out指令设置端口0x1f2
        mov eax, esi                      ; 恢复 eax
        
        ; 第2步: 在端口0x1f3～0x1f6中写入要读取的扇区的LBA地址   
        mov dx, 0x1f3                     
        out dx, al                        ; LBA地址的0~7位写入端口0x1f3
                                          
        mov cl, 0x8                       
        shr eax, cl                       ; eax逻辑右移8位
        mov dx, 0x1f4
        out dx, al                        ; LBA地址的8~15位写入端口0x1f4
                                          
        shr eax, cl                       ; eax逻辑右移8位
        mov dx, 0x1f5
        out dx, al                        ; LBA地址的16~23位写入端口0x1f5
                                          
        shr eax, cl                       ; eax逻辑右移8位
        and al, 0x0f                      ; 只保留最低4位
        or al, 0xe0                       ; 将高4位设置为 1110（7~4位），第4位置0表示主盘，第6位置1表示LBA模式
        mov dx, 0x1f6
        out dx, al                        ; LBA地址的24~27位写入端口0x1f6的0~3位
        
        ; 第3步: 向端口0x1f7（写操作时为command端口）中写入读命令0x20
        mov dx, 0x1f7
        mov al, 0x20
        out dx, al                        ; 此后硬盘自动开始工作
        
        ; 第4步: 循环检测硬盘状态
.not_ready:
        nop                               ; 无操作(no operation)，相当于简单的sleep
        in al, dx                         ; dx=0x1f7（读操作时为status端口），读取硬盘状态
        ; and al, 0x88                      ; 只取第4位与第7位的值（第4位为1表示硬盘控制器已准备好数据传输，第7位为1表示硬盘忙）
        and al, 0x08                      ; 只取第4位的值（第4位为1表示硬盘控制器已准备好数据传输）
        cmp al, 0x08                      ; 比较 al 与 0x08（将 al与0x08相减，并修改flags寄存器的ZF、SF标志位，但不会修改al的值）
        jnz .not_ready                    ; 判断flags的ZF标志位，若不为0则跳转至.not_ready，循环检查
        
        ; 第5步: 从端口0x1f0(data端口)读取数据
        mov ax, di                        ; 在开头di备份了cx，表示要读取的扇区数
        mov dx, 256                       ; data端口为16位寄存器，一次读取2B，每个扇区512B，要读256次
                                          
                                          ; 计算要读取的总次数。
                                          ; 若指令为 mul dl，则做8位乘法al*dl，将结果存进ax                                        
        mul dx                            ; dx为16位，故mul做16位乘法ax*dx，将结果高16位存进dx,低16位存进ax。
        mov cx, ax
        mov dx, 0x1f0                     ; 0x1f0在读操作时为data端口，存放硬盘准备好的数据

.go_on_read:
        in ax, dx                         
                                          ; 将数据写入ds:bx处，这里ds为0,所以是内存地址就是bx
        mov [bx], ax                      ; 值得注意，实模式下bx最大为0xffff，故不改变ds的情况下，最大能加载64KB的数据(在这里是够用的)
        add bx, 2
        loop .go_on_read                  ; 将cx的值减1,并检查结果是否为0,不为0则跳转到.go_on_read，否则执行下一句
        ret


; -------------------------------------------------------------------------------------------------------------------------
        ; 填充剩余的字节，并将最后两字节赋为魔数 0x55, 0xaa
        ; ------------------------------
        ; 输入: 无
; -------------------------------------------------------------------------------------------------------------------------	
	                                  ; -----------------------------------------------
	                                  ; times T expr: 重复expr的操作T次
	                                  ; -----------------------------------------------
	times 510-($-$$) db 0             ; 将当前扇区剩余的数据都填充为0
	db 0x55, 0xaa                     ; 最后两个字节赋值为魔数 0x55, 0xaa，声明此扇区是MBR
